package com.leyou.search.service.Impl;

import com.leyou.common.dto.PageDTO;
import com.leyou.common.exceptions.LyException;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.SkuDTO;
import com.leyou.item.dto.SpecParamDTO;
import com.leyou.item.dto.SpuDTO;
import com.leyou.search.dto.SearchParamDTO;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.service.SearchService;
import com.leyou.starter.elastic.dto.PageInfo;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.stream.Collectors;

import static com.leyou.search.constants.SearchConstants.*;

@Log4j2
@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private GoodsRepository repository;

    @Autowired
    private ItemClient itemClient;
    /**
     * 初始化索引库
     */
    @Override
    public void createIndexAndMapping() {
        //先删除索引库
        try {
            repository.deleteIndex();
        } catch (Exception e) {
            log.info("删除失败，索引库不存在，需要重新创建", e);
        }
        //初始化索引库
        repository.createIndex(INDEX_SOURCE);
        log.info("索引库创建成功");
    }

    /**
     * 查询数据加载到索引库
     */
    @Override
    public void loadData() {
        log.info("准备开始加载商品数据到Elasticsearch");
        //分页查询所有，定义分页信息
        int page = 1;
        int rows = 50;
        //由于一页数据有限所以需要一直循环添加
        while (true){
            log.info("开始查询第{}页的商品信息" , page );
            //查询商品信息
            PageDTO<SpuDTO> pages = itemClient.querySpuByPage(page, rows, true, null, null, null);
            //将数据放到数组中方便添加
            List<SpuDTO> spuList = pages.getItems();
            //准备一个空goods集合对象，将查询到的spu集合添加到goods集合中
            List<Goods> goodsList = spuList.stream().map(this::buildGoods).collect(Collectors.toList());
            //添加数据到索引库
            repository.saveAll(goodsList);
            log.info("第{}页数据导入成功");

            //当前页查询完成需要换页查询
            page++;
            //一直查询是会到最后一条数据，在最后一页结束
            if (page > pages.getTotalPage()){
                log.info("查询玩所有数据,一共{}页，{}条数据");
                // 没有数据就结束查询
                break;
            }
        }
    }

    @Override
    public Mono<List<String>> querySuggestion(String key) {
        //判断key是否有值
        if (StringUtils.isBlank(key)){
            throw new LyException(400 , "输入的值不能为空");
        }
        return repository.suggestBySingleField( SUGGESTION_FIELD , key);
    }

    /**
     *  分页搜索商品数据
     * @param paramDTO
     * @return
     */
    @Override
    public Mono<PageInfo<Goods>> serchGoods(SearchParamDTO paramDTO) {
        //创建一个空对象
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //关键字搜索
        String key = paramDTO.getKey();
        if (StringUtils.isBlank(key)){
            //过搜索为空不到下一步
            throw new LyException(500 , "关键词搜索不能为空");
        }
        searchSourceBuilder.query(QueryBuilders.matchQuery(SEARCH_FIELD,key));

        //过滤source
        searchSourceBuilder.fetchSource(SOURCE_FIELD,null);

        //排序
        //获取升降序字段和是升序还是降序
        String sortBy = paramDTO.getSortBy();//字段
        Boolean desc = paramDTO.getDesc();//默认是空
        if (StringUtils.isNotBlank(sortBy)){
            //排序字段不为空，就排序 判断desc是true还是false
            searchSourceBuilder.sort(sortBy,desc ? SortOrder.DESC : SortOrder.ASC);
        }

        //分页
        int size = paramDTO.getSize();
        searchSourceBuilder.from(paramDTO.getFrom()).size(size);

        //对查询到的关键词高亮
        searchSourceBuilder.highlighter( new HighlightBuilder().field(SEARCH_FIELD).preTags("<am>").postTags("</am>"));

        //搜索
        return repository.queryBySourceBuilderForPageHighlight(searchSourceBuilder);
    }

    @Override
    public void saveById(Long spuId) {
        //根据spuId查询spu
        SpuDTO spuDTO = itemClient.queryGoodsById(spuId);
        //构建一个goods
        Goods goods = buildGoods(spuDTO);
        //新增
        repository.save(goods);
    }

    @Override
    public void deleteById(Long spuId) {
        //删除
        repository.deleteById(spuId);

    }

    private Goods buildGoods(SpuDTO spuDTO){
        //1.商品标签,包含商品标签，品牌，分类，规格参数...
        String categoryName = spuDTO.getCategoryName();
        StringBuilder title = new StringBuilder(spuDTO.getTitle())
                .append(spuDTO.getBrandName())
                .append(categoryName);

        //2.自动补全的提示字段，包含分类名称、品牌名称、商品名称
        //给一个空集合，添加数据
        Set<String> suggestion = new HashSet<>();
        //添加商品名
        suggestion.add(spuDTO.getName());
        //添加品牌名
        suggestion.add(spuDTO.getBrandName());
        //切割后添加类型
        String[] names = StringUtils.split(categoryName, "/");
        Collections.addAll(suggestion,names);

        //3.商品销量
        //4.商品价格
        // 查询出spu下的所有的sku
        List<SkuDTO> skus = spuDTO.getSkus();
        Long spuId = spuDTO.getId();
        if (skus == null) {
            skus = itemClient.querySkuBySpuId(spuId);
        }
        //定义一个初始销售量
        long sold = 0;
        // 创建一个唯一有序的集合存放价格
        Set<Long> prices = new TreeSet<>();
        //集合处理
        for (SkuDTO sku : skus) {
            // 计算销量
            sold += sku.getSold();
            // 价格
            prices.add(sku.getPrice());
        }

        // 5.商品图片
        String image = StringUtils.substringBefore(skus.get(0).getImages(), ",");

        // 6查询规格参数键值对
        // 规格参数键值对 [{name:"CPU频率", value:"2.5GHz"},{name:"CPU型号", value:"骁龙875"}]
        List<SpecParamDTO> params = itemClient.querySpecsValues(spuId, true);
        //放到一个集合中
        List<Map<String,Object>> specs = new ArrayList<>(params.size());
        for (SpecParamDTO param : params) {
            // 创建map
            Map<String,Object> map = new HashMap<>();
            map.put("name", param.getName());
            map.put("value", chooseSegment(param));
            specs.add(map);
        }


        //创建一个空的goods来存放数据
        Goods goods = new Goods();
        //新增时间
        goods.setUpdateTime(new Date());//当前添加时间
        //商品标题，用于搜索
        goods.setTitle(title.toString());
        //自动补全的候选字段，可以包含多个值，例如分类名称、品牌名称、商品名称
        goods.setSuggestion(suggestion);
        //商品规格参数
        goods.setSpecs(specs);
        //商品销量
        goods.setSold(sold);
        //sku价格集合
        goods.setPrices(prices);
        //sku图片
        goods.setImage(image);
        //商品id
        goods.setId(spuDTO.getId());
        //分类id
        goods.setCategoryId(spuDTO.getCid3());
        //商品品牌id
        goods.setBrandId(spuDTO.getBrandId());
        return goods;
    }

    private Object chooseSegment(SpecParamDTO specParamDTO){

        Object value = specParamDTO.getValue();
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        if(!specParamDTO.getNumeric() || StringUtils.isBlank(specParamDTO.getSegments()) || value instanceof Collection){
            return value;
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : specParamDTO.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + specParamDTO.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + specParamDTO.getUnit() + "以下";
                } else {
                    result = segment + specParamDTO.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }
}
